Skip to content

feat(python): workflow context propagation quickstart#1309

Open
nelson-parente wants to merge 7 commits into
dapr:release-1.18from
nelson-parente:feat/wf-ctx-propagation-python
Open

feat(python): workflow context propagation quickstart#1309
nelson-parente wants to merge 7 commits into
dapr:release-1.18from
nelson-parente:feat/wf-ctx-propagation-python

Conversation

@nelson-parente
Copy link
Copy Markdown
Contributor

@nelson-parente nelson-parente commented May 19, 2026

Summary

Python sibling of @cicoyle's canonical Go quickstart for workflow history propagation (Dapr 1.18). Same patient intake / e-prescribing scenario, same 3-level hierarchy, same workflow/activity names — so the Python and Go examples can be compared 1:1.

Scenario

Patient intake / e-prescribing pipeline. A compliance audit and a pharmacy dispense step refuse to act unless the propagated history proves the required upstream checks (insurance, allergies, drug interactions) actually ran.

PatientIntake (root)
  └─ VerifyInsurance       (activity, no propagation)
  └─ PrescribeMedication   (child wf, PropagationScope.LINEAGE)
        └─ CheckAllergies         (activity, no propagation)
        └─ ScreenDrugInteractions (activity, no propagation)
        └─ ComplianceAudit        (grandchild wf, PropagationScope.LINEAGE)
        |      reads PatientIntake + PrescribeMedication events
        └─ DispenseMedication     (activity, PropagationScope.OWN_HISTORY)
               reads PrescribeMedication events only
               refuses to dispense if the screening lineage is missing

ComplianceAudit uses LINEAGE to verify the full ancestor chain. DispenseMedication uses OWN_HISTORY as a trust boundary — the pharmacy step doesn't get to see the upstream patient-intake chain.

Two scenarios (matches the Go demo)

The app schedules both back-to-back and exits on its own — no Ctrl+C needed:

  1. Lineage forwardedPrescribeMedication calls DispenseMedication with PropagationScope.OWN_HISTORY; pharmacy verifies and dispenses.
  2. Lineage withheldPrescribeMedication calls DispenseMedication without propagation; pharmacy receives no history and refuses with a refused DispenseResult.

Python API used (from dapr/python-sdk#1025)

  • PropagationScope.LINEAGE / PropagationScope.OWN_HISTORY
  • propagation= kwarg on ctx.call_child_workflow() and ctx.call_activity()
  • ctx.get_propagated_history()PropagatedHistory | None
  • PropagatedHistory.get_last_workflow_by_name(name)WorkflowResult
  • WorkflowResult.get_last_activity_by_name(name)ActivityResult (with .completed, .output)
  • PropagationNotFoundError

Replay safety

All print() calls inside workflows are guarded by if not ctx.is_replaying: so they only fire on the live execution, not on each durable replay.

Files

tutorials/workflow/python/history-propagation/
├── README.md          # mirrors the Go README, adapted for Python
├── dapr.yaml          # dapr run -f . config (appID=patient-app)
├── makefile           # wires the example into `make validate`
├── app.py             # registry + worker setup, schedules both scenarios
├── models.py          # PatientRecord, ComplianceResult, DispenseResult
├── workflow.py        # workflow + activity definitions, history helpers
└── requirements.txt   # Python deps (dapr-ext-workflow==1.18.0rc0)

Test plan

  • pip3 install -r requirements.txt against dapr-ext-workflow==1.18.0rc0
  • dapr run -f . against a Dapr 1.18 RC sidecar
  • Scenario 1: ComplianceAudit reads full ancestor history via LINEAGE (sees PatientIntake + PrescribeMedication); DispenseMedication sees only PrescribeMedication events via OWN_HISTORY and dispenses
  • Scenario 2: DispenseMedication receives no propagated history and returns refused, PrescribeMedication reports pharmacy refused to dispense
  • Older sidecar (pre-1.18) gracefully returns None from get_propagated_history() without crash

References

Ports Cassie Coyle's Go-SDK reference example (dapr/go-sdk#823) to Python.
Demonstrates PropagationScope.LINEAGE and PropagationScope.OWN_HISTORY in a
fraud-detection payment scenario with a 3-level workflow hierarchy.

Requires Dapr 1.18+ (dapr/dapr#9810) and dapr-ext-workflow 1.18+ (dapr/python-sdk#1025).

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
Aligns the Python workflow history propagation quickstart with the canonical
Go reference (dapr/go-sdk#823, dapr#1315) so all SDK quickstarts
share the same patient intake / e-prescribing scenario.

- Swap credit-card/fraud scenario for patient-intake/e-prescribing
- Adopt PatientIntake -> PrescribeMedication -> ComplianceAudit hierarchy
- Add is_replaying guards around all print() calls inside workflows
- Use Cassie's PascalCase activity/workflow names for cross-SDK consistency

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@nelson-parente nelson-parente marked this pull request as ready for review May 21, 2026 09:54
@nelson-parente nelson-parente requested review from a team as code owners May 21, 2026 09:54
@nelson-parente nelson-parente changed the title [DRAFT] feat(python): workflow context propagation quickstart feat(python): workflow context propagation quickstart May 21, 2026
Aligns the Python workflow history propagation quickstart with Cassie's
canonical Go version that merged into release-1.18
(dapr#1315, tutorials/workflow/go/history-propagation).

Changes to match the canonical structure:

- Move from workflows/python/sdk-context-propagation/order-processor/ to
  tutorials/workflow/python/history-propagation/, matching the Go sibling
  at tutorials/workflow/go/history-propagation/
- Split the monolithic app.py into app.py / workflow.py / models.py,
  mirroring main.go / workflow.go / models.go
- Add ForwardLineage flag to PatientRecord and run both scenarios
  back-to-back (happy path + negative), purging state after each so the
  app exits on its own
- Make DispenseMedication refuse to dispense when no propagated history
  is received, returning a refused DispenseResult with a Reason field
- Match the Go README: title, propagation-scope table, STEP markers,
  expected output for both scenarios
- dapr.yaml: appID=patient-app (was order-processor),
  resourcesPath=../../resources (was ../../components),
  appLogDestination/daprdLogDestination=console to match sister Python
  tutorials and Cassie's Go example
- Use correct Python SDK API names: get_last_workflow_by_name /
  get_last_activity_by_name (the earlier draft used get_workflow_by_name
  / get_activity_by_name, which were renamed in dapr/python-sdk#1025)

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@nelson-parente nelson-parente changed the base branch from master to release-1.18 May 28, 2026 21:26
@nelson-parente
Copy link
Copy Markdown
Contributor Author

Updated to align with @cicoyle's now-merged Go quickstart at tutorials/workflow/go/history-propagation (#1315). Summary of what changed since the last revision:

Location & target branch

  • Moved files from workflows/python/sdk-context-propagation/order-processor/tutorials/workflow/python/history-propagation/, matching the Go sibling.
  • Retargeted PR base from masterrelease-1.18 (where Cassie's PR landed).

File layout

  • Split the previous single app.py into three files mirroring main.go / workflow.go / models.go:
    • app.py — registry + worker setup, schedules both scenarios.
    • workflow.py — workflow and activity definitions, history helpers.
    • models.pyPatientRecord, ComplianceResult, DispenseResult dataclasses.

Behavioural alignment with the Go canonical version

  • Added forward_lineage: bool field to PatientRecord to control whether PrescribeMedication propagates its history to DispenseMedication.
  • The app now runs two scenarios back-to-back (was: a single happy path):
    1. forward_lineage=True → pharmacy verifies the propagated history and dispenses.
    2. forward_lineage=False → pharmacy receives no propagated history and refuses with a refused DispenseResult (added reason field).
  • After each scenario, the workflow state is purged so the demo exits on its own.

dapr.yaml

  • appID: order-processorappID: patient-app (matches the Go example's patient-app).
  • resourcesPath: ../../componentsresourcesPath: ../../resources (matches the convention used by sibling Python tutorials under tutorials/workflow/python/).
  • Added appLogDestination: console and daprdLogDestination: console.

Python SDK API

  • Switched to the correct method names from dapr-ext-workflow 1.18: get_last_workflow_by_name / get_last_activity_by_name (the previous draft used get_workflow_by_name / get_activity_by_name, which were renamed in Add history propagation python-sdk#1025).

README

  • Rewrote to mirror the Go README's structure: title, propagation-scope table, key-demonstration block, both scenarios documented, <!-- STEP --> markers for make validate, expected-output snippets for both scenarios, file map at the bottom.

No SDK changes were required — the dependency stays at dapr-ext-workflow==1.18.0rc0 from #1307.

Copy link
Copy Markdown
Contributor

@alicejgibbons alicejgibbons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor things

proof — in the propagated history — that the required upstream checks
(insurance, allergies, drug interactions) actually ran.

This is the Python sibling of the canonical Go quickstart in
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this

[`tutorials/workflow/go/history-propagation`](../../go/history-propagation),
itself based on [dapr/go-sdk#823](https://github.com/dapr/go-sdk/pull/823).
Python runtime support landed in
[dapr/python-sdk#1025](https://github.com/dapr/python-sdk/pull/1025).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ai junk


Requires Dapr `1.18.0+` (workflow history propagation), `go-sdk v1.15.0+`,
and `durabletask-go v0.12.0+`.
Requires Dapr `1.18.0+` (workflow history propagation),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this simplier just say 1.18+, no need to specify the sdk

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove


of scope for this quickstart.

## Files
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REmove this

)

# Step 4: Dispense the medication.
# Happy path: attach PropagationScope.OWN_HISTORY — the pharmacy sees our
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general can you make most of these comments simplier? There is a lot of repetition

alicejgibbons and others added 2 commits May 29, 2026 11:00
…code

- README: drop the Go-sibling reference paragraph
- README: simplify the requirements line to just "Requires Dapr 1.18+"
- README: drop the "exits on its own — no Ctrl+C needed" sentence
- README: drop the "## Files" tree at the bottom
- workflow.py: trim repetitive docstrings and inline Step comments;
  the step prints already carry the same information

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@nelson-parente
Copy link
Copy Markdown
Contributor Author

Thanks @alicejgibbons — addressed all six in 015fb51:

README

  • Dropped the Go-sibling paragraph (L14–18).
  • Simplified the requirements line to Requires Dapr 1.18+. (L114).
  • Dropped The app runs both scenarios once and exits on its own — no Ctrl+C needed. (L158).
  • Dropped the ## Files tree at the bottom (L187).

workflow.py

  • Trimmed the repetitive function docstrings and removed the per-step inline comments — the Step N: ... print statements already carry the same info. Kept one short comment on Step 4 since that's the part the reader actually needs to understand (the two propagation modes side-by-side).

- Merge 'Key demonstration' into 'Scenarios' so the LINEAGE/OWN_HISTORY
  behavior is explained once instead of three times.
- Cut the unsigned-history warning note from a paragraph to one line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants